package gutils

import (
	"math/rand"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/shopspring/decimal"
)

// randInt returns a random integer in [min, max)
func RandInt(min, max int) int {
	if min >= max {
		return min
	}
	return min + rand.Intn(max-min)
}

// randIntUnder returns a random integer in [0, n)
func RandIntUnder(n int) int {
	if n <= 0 {
		return 0
	}
	return rand.Intn(n)
}

// randString returns a random string of specified length
func RandString(length int) string {
	return RandStringByCharset("", length)
}

// randStringByCharset returns a random string using specified charset
func RandStringByCharset(charset string, length int) string {
	if length <= 0 {
		return ""
	}
	if charset == "" {
		charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	}

	// 把字符串拆成 rune 切片
	runes := []rune(charset)
	n := len(runes)
	if n == 0 {
		return ""
	}

	var sb strings.Builder
	sb.Grow(length * utf8.UTFMax) // 预分配空间，考虑最坏情况
	for i := 0; i < length; i++ {
		sb.WriteRune(runes[rand.Intn(n)])
	}
	return sb.String()
}

// randFloat64 returns a random float64 in [0.0, 1.0)
func RandFloat64() float64 {
	return rand.Float64()
}

// init initializes the random number generator
func init() {
	rand.New(rand.NewSource(time.Now().UnixNano()))
}

// Allocate total amount according to certain deviation
/*
var m = randomAllocate{
	AmountTotal:      decimal.NewFromFloat(1243.0),
	CountTotal:       42,
	MaxOffsetPercent: decimal.NewFromFloat(0.3),
}
a, p := m.run()

totalAmount := decimal.NewFromInt(0)
totalPercent := decimal.NewFromInt(0)
for i, v := range a {
	fmt.Println(v.String())
	totalAmount = totalAmount.Add(v)
	totalPercent = totalPercent.Add(p[i])
}
*/
type RandomAllocate struct {
	AmountTotal        decimal.Decimal   // Total amount
	MaxPercentOfOffset decimal.Decimal   // Max percent of offset
	CountTotal         uint              // Allocate times
	ResultArrayAmount  []decimal.Decimal // Allocated amount array
	ResultArrayPercent []decimal.Decimal // Allocated percent array
	countCurrent       uint              // Current allocate times
	dynamicAverage     decimal.Decimal   // Average, change dynamically
	consumeTotal       decimal.Decimal   // Total consumed amount
}

func (r *RandomAllocate) doItem(LeftCount uint) bool {
	var consume decimal.Decimal
	end := false

	rdm := float64(RandInt(60, 140)) / float64(100)
	consume = r.dynamicAverage.Mul(decimal.NewFromFloat(rdm))

	maxDiff := r.AmountTotal.Sub(r.consumeTotal)
	if consume.GreaterThan(maxDiff) {
		consume = maxDiff
		end = true
	}

	r.ResultArrayAmount = append(r.ResultArrayAmount, r.dynamicAverage.Mul(decimal.NewFromFloat(rdm)))
	r.ResultArrayPercent = append(r.ResultArrayPercent, consume.Div(r.AmountTotal))
	r.consumeTotal = r.consumeTotal.Add(consume)
	return end
}

func (r *RandomAllocate) run() ([]decimal.Decimal, []decimal.Decimal) {
	r.countCurrent = 1

	for r.countCurrent <= r.CountTotal {
		LeftCount := r.CountTotal - r.countCurrent + 1
		amountLeft := r.AmountTotal.Sub(r.consumeTotal)
		if LeftCount == 1 {
			r.ResultArrayAmount = append(r.ResultArrayAmount, amountLeft)
			r.ResultArrayPercent = append(r.ResultArrayPercent, amountLeft.Div(r.AmountTotal))
		} else {
			LeftCountDec := decimal.NewFromInt(int64(LeftCount))
			r.dynamicAverage = amountLeft.Div(LeftCountDec)
			if r.doItem(LeftCount) {
				break
			}
		}
		r.countCurrent = r.countCurrent + 1
	}

	return r.ResultArrayAmount, r.ResultArrayPercent
}
